package com.msh.artascope.devcloud.service.service.impl;


import com.msh.artascope.devcloud.client.po.DomainConfigPo;
import com.msh.artascope.devcloud.client.po.ModuleNetworkPo;
import com.msh.artascope.devcloud.client.po.ModulePo;
import com.msh.artascope.devcloud.client.po.ModuleReverseProxyPo;
import com.msh.artascope.devcloud.client.po.ModuleRunningPo;
import com.msh.artascope.devcloud.client.qo.ModuleNetworkQo;
import com.msh.artascope.devcloud.client.qo.ModuleReverseProxyQo;
import com.msh.artascope.devcloud.client.qo.ModuleRunningQo;
import com.msh.artascope.devcloud.service.dao.ModuleReverseProxyDao;
import com.msh.artascope.devcloud.service.define.DevCloudDef;
import com.msh.artascope.devcloud.service.model.query.ModuleReverseProxyQueryVo;
import com.msh.artascope.devcloud.service.model.vo.ModuleReverseProxyVo;
import com.msh.artascope.devcloud.service.service.DomainConfigService;
import com.msh.artascope.devcloud.service.service.KubernetesConfigService;
import com.msh.artascope.devcloud.service.service.ModuleNetworkService;
import com.msh.artascope.devcloud.service.service.ModuleReverseProxyService;
import com.msh.artascope.devcloud.service.service.ModuleRunningService;
import com.msh.artascope.devcloud.service.service.ModuleService;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.IstioVsHttpMatchInfo;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.IstioVsInfo;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.IstioVsRoute;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.K8sInfo;
import com.msh.frame.client.base.TenantBaseServiceImpl;
import com.msh.frame.client.common.Result;
import com.msh.frame.client.exception.BizException;
import com.msh.frame.interfaces.IdGenerateable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
 * @author shihu
 * @email m-sh@qq.com
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class ModuleReverseProxyServiceImpl extends
    TenantBaseServiceImpl<ModuleReverseProxyPo, ModuleReverseProxyQo> implements
    ModuleReverseProxyService {

  @Resource
  private DomainConfigService domainConfigService;

  @Resource
  private ModuleReverseProxyDao moduleReverseProxyDao;

  @Resource
  private IdGenerateable idGenerateable;
  @Resource
  private KubernetesConfigService kubernetesConfigService;
  @Resource
  private ModuleService moduleService;
  @Resource
  private ModuleNetworkService moduleNetworkService;
  @Resource
  private K8sWrapper k8sWrapper;
  @Resource
  @Lazy
  private ModuleRunningService moduleRunningService;


  @Override
  public long insert(ModuleReverseProxyPo param) {
    param.setId(idGenerateable.getUniqueId());
    return super.insert(param);
  }

  @Override
  public int insertCollection(Collection<ModuleReverseProxyPo> param) {
    Long[] uniqueIds = idGenerateable.getUniqueIds(param.size());
    int i = 0;
    for (ModuleReverseProxyPo next : param) {
      next.setId(uniqueIds[i++]);
    }
    return super.insertCollection(param);
  }


  private Long insertWithVirtualService(Long moduleId, Long domainConfigId, String requestPath,
      String appPath, Long tenantId) {

    DomainConfigPo domainConfigPo = domainConfigService.get(domainConfigId, tenantId);
    BizException.doThrowIfNull("域名配置不存在", domainConfigPo);

    Long envId = domainConfigPo.getEnvId();
    K8sInfo k8s = kubernetesConfigService.getK8s(envId, tenantId);
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);

    List<ModuleReverseProxyPo> list = list(
        new ModuleReverseProxyQo().setDomainConfigId(domainConfigId).setTenantId(tenantId));

    BizException.doThrowIfTrue("域名的请求路径已存在",
        list.stream().anyMatch(p -> p.getRequestPath().equals(requestPath)));

    ModuleReverseProxyPo po = new ModuleReverseProxyPo();
    po.setModuleId(moduleId);
    po.setDomainConfigId(domainConfigId);
    po.setRequestPath(requestPath);
    po.setAppPath(appPath);
    po.setEnvId(domainConfigPo.getEnvId());
    po.setTenantId(tenantId);

    long insert = insert(po);
    list.add(po);

    reloadVirtualServiceByDomainConfig(domainConfigPo, k8s, tenantId);
    return insert;
  }

  private void deleteWithVirtualService(ModuleReverseProxyPo po, Long tenantId) {
    Long envId = po.getEnvId();
    DomainConfigPo domainConfigPo = domainConfigService.getByEnvId(envId, tenantId);

    delete(po.getId(), tenantId);
    reloadVirtualServiceByDomainConfig(domainConfigPo, tenantId);
  }

  @Override
  public void reloadVirtualServiceByModuleId(Long moduleId, K8sInfo k8sInfo, Long tenantId) {
    List<ModuleReverseProxyPo> reverseProxyPoList = list(
        new ModuleReverseProxyQo().setModuleId(moduleId).setTenantId(tenantId));
    reloadVirtualServiceByModuleId(reverseProxyPoList, k8sInfo, tenantId);
  }

  private void reloadVirtualServiceByModuleId(List<ModuleReverseProxyPo> reverseProxyPoList,
      K8sInfo k8sInfo, Long tenantId) {
    if (CollectionUtils.isEmpty(reverseProxyPoList)) {
      return;
    }
    Set<Long> domainConfigIdSet = reverseProxyPoList.stream()
        .map(ModuleReverseProxyPo::getDomainConfigId)
        .collect(Collectors.toSet());
    List<DomainConfigPo> domainConfigPoList = domainConfigService.getByIds(domainConfigIdSet,
        tenantId);
    if (CollectionUtils.isEmpty(domainConfigPoList)) {
      return;
    }
    domainConfigPoList.forEach(d -> {
      reloadVirtualServiceByDomainConfig(d, k8sInfo, tenantId);
    });
  }

  /**
   * 根据配置刷新vs信息
   *
   * @param domainConfig
   * @param tenantId
   */
  private void reloadVirtualServiceByDomainConfig(DomainConfigPo domainConfig, Long tenantId) {
    K8sInfo k8s = kubernetesConfigService.getK8s(domainConfig.getEnvId(), tenantId);
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    reloadVirtualServiceByDomainConfig(domainConfig, k8s, tenantId);
  }

  /**
   * 根据配置刷新vs信息
   *
   * @param domainConfig
   * @param k8s
   * @param tenantId
   */
  private void reloadVirtualServiceByDomainConfig(DomainConfigPo domainConfig, K8sInfo k8s,
      Long tenantId) {
    String domain = domainConfig.getDomain();
    List<ModuleReverseProxyPo> list = list(
        new ModuleReverseProxyQo().setDomainConfigId(domainConfig.getId()).setTenantId(tenantId));
    String vsName = DevCloudDef.getGatewayName(domain);
    IstioVsInfo istioVsInfo = buildIstioVsInfo(domain, k8s.getNamespace(), list, tenantId);
    if (null == istioVsInfo) {
      // ingress中域名全部被删除, 删除域名配置
      k8sWrapper.deleteIstioVirtualService(k8s, vsName);
    } else {
      k8sWrapper.saveIstioVirtualService(k8s, vsName, istioVsInfo);
    }
  }


  private IstioVsInfo buildIstioVsInfo(String domain, String namespace,
      List<ModuleReverseProxyPo> list, Long tenantId) {
    if (CollectionUtils.isEmpty(list)) {
      return null;
    }
    String gatewayName = DevCloudDef.getGatewayName(domain);
    IstioVsInfo istioVsInfo = new IstioVsInfo()
        .setName(gatewayName)
        .setNamespace(namespace)
        .setDomain(domain)
        .setGatewayName(gatewayName);
    Set<Long> moduleIdSet = list.stream().map(ModuleReverseProxyPo::getModuleId)
        .collect(Collectors.toSet());
    Map<Long, ModulePo> moduleMap = moduleService.map(moduleIdSet, tenantId);
    Map<Long, ModuleNetworkPo> moduleNetworkMap = moduleNetworkService.list(
            new ModuleNetworkQo().setModuleIdIn(moduleIdSet).setTenantId(tenantId))
        .stream().collect(Collectors.toMap(ModuleNetworkPo::getModuleId, Function.identity()));

    Map<Long, List<ModuleRunningPo>> runngingGroup = moduleRunningService.list(
            new ModuleRunningQo().setModuleIdIn(moduleIdSet).setTenantId(tenantId))
        .stream().collect(Collectors.groupingBy(ModuleRunningPo::getModuleId));

    List<IstioVsHttpMatchInfo> matchList = list.stream().filter(
            p -> moduleMap.containsKey(p.getModuleId()) && moduleNetworkMap.containsKey(
                p.getModuleId()))
        .sorted((o1, o2) -> o2.getRequestPath().length() - o1.getRequestPath().length())
        .flatMap(p -> {
          List<ModuleRunningPo> moduleRunningPoList = runngingGroup.get(p.getModuleId());
          String moduleName = moduleMap.get(p.getModuleId()).getName();
          if (null == moduleRunningPoList || moduleRunningPoList.size() == 1) {
            IstioVsHttpMatchInfo match = new IstioVsHttpMatchInfo()
                .setRequestPath(DevCloudDef.getFormatPath(p.getRequestPath()))
                .setAppPath(DevCloudDef.getFormatPath(p.getAppPath()))
                .setRoute(Collections.singletonList(new IstioVsRoute().setHost(moduleName)
                    .setPort(moduleNetworkMap.get(p.getModuleId()).getAppPort())));
            return Stream.of(match);
          }
          Map<String, IstioVsHttpMatchInfo> matchMap = new HashMap<>();
          moduleRunningPoList.stream()
              .sorted((o1, o2) -> {
                if (StringUtils.hasText(o1.getHeaderKey())) {
                  return -1;
                } else if (StringUtils.hasText(o2.getHeaderKey())) {
                  return 1;
                }
                return 0;
              }).forEach(r -> {
                IstioVsRoute istioVsRoute = new IstioVsRoute().setHost(moduleName)
                    .setSubset(DevCloudDef.getVersion(r.getVersion()));
                IstioVsHttpMatchInfo istioVsHttpMatchInfo = matchMap.computeIfAbsent(
                    r.getHeaderKey() + "#" + r.getHeaderValue(), o -> new IstioVsHttpMatchInfo()
                        .setRequestPath(DevCloudDef.getFormatPath(p.getRequestPath()))
                        .setAppPath(DevCloudDef.getFormatPath(p.getAppPath()))
                        .setHeaderKey(r.getHeaderKey())
                        .setHeaderValue(r.getHeaderValue())
                        .setRoute(new LinkedList<>()));
                istioVsHttpMatchInfo.getRoute().add(istioVsRoute);
              });
          return matchMap.values().stream()
              .peek(IstioVsHttpMatchInfo::initWeight).sorted((o1, o2) -> {
                if (StringUtils.hasText(o1.getHeaderKey())) {
                  return -1;
                } else if (StringUtils.hasText(o2.getHeaderKey())) {
                  return 1;
                }
                return 0;
              });
        }).collect(Collectors.toList());
    istioVsInfo.setMatchs(matchList);
    return istioVsInfo;
  }
// ----------前端vo处理-------------------------------------------------------

  private ModuleReverseProxyVo toVo(ModuleReverseProxyPo po, DomainConfigPo domainConfigPo) {
    ModuleReverseProxyVo vo = po.convert(ModuleReverseProxyVo.class);
    if (null != domainConfigPo) {
      vo.setDomainConfigIdDesc(domainConfigPo.getDomain());
    }
    return vo;
  }

  private List<ModuleReverseProxyVo> toVoList(List<ModuleReverseProxyPo> list, Long tenantId) {
    if (CollectionUtils.isEmpty(list)) {
      return Collections.emptyList();
    }
    Set<Long> domainConfigIdSet = list
        .stream()
        .map(ModuleReverseProxyPo::getDomainConfigId)
        .collect(Collectors.toSet());
    Map<Long, DomainConfigPo> domainConfigIdMap = domainConfigService.map(domainConfigIdSet,
        tenantId);

    return list.stream().map(po -> {
      return toVo(po, domainConfigIdMap.get(po.getDomainConfigId()));
    }).collect(Collectors.toList());
  }

  @Override
  public int deleteByModuleId(Long moduleId, Long tenantId) {
    List<ModuleReverseProxyPo> reverseProxyPoList = list(
        new ModuleReverseProxyQo().setModuleId(moduleId).setTenantId(tenantId));
    if (CollectionUtils.isEmpty(reverseProxyPoList)) {
      return 0;
    }
    Set<Long> idSet = reverseProxyPoList.stream().map(ModuleReverseProxyPo::getId)
        .collect(Collectors.toSet());
    int i = deleteByIds(idSet, tenantId);

    K8sInfo k8s = kubernetesConfigService.getK8s(reverseProxyPoList.get(0).getEnvId(), tenantId);
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    reloadVirtualServiceByModuleId(reverseProxyPoList, k8s, tenantId);
    return i;
  }

  @Override
  @Transactional
  public Long insertVo(ModuleReverseProxyVo vo) {
    return insertWithVirtualService(vo.getModuleId(), vo.getDomainConfigId(), vo.getRequestPath(),
        vo.getAppPath(), vo.getTenantId());
  }

  @Override
  @Transactional
  public void deleteVo(Long id, Long tenantId) {
    ModuleReverseProxyPo moduleReverseProxyPo = get(id, tenantId);
    if (null == moduleReverseProxyPo) {
      return;
    }
    deleteWithVirtualService(moduleReverseProxyPo, tenantId);
  }

  @Override
  public ModuleReverseProxyVo getVo(Long id, Long tenantId) {
    ModuleReverseProxyPo po = this.get(id, tenantId);
    if (null == po) {
      return null;
    }
    DomainConfigPo domainConfigPo = domainConfigService.get(po.getDomainConfigId(), tenantId);
    ModuleReverseProxyVo vo = toVo(po, domainConfigPo);

    return vo;
  }

  @Override
  public List<ModuleReverseProxyVo> listVo(ModuleReverseProxyQueryVo q) {
    ModuleReverseProxyQo qo = q.convert(ModuleReverseProxyQo.class);
    List<ModuleReverseProxyPo> poList = this.list(qo);
    List<ModuleReverseProxyVo> voList = toVoList(poList, qo.getTenantId());

    return voList;
  }

  @Override
  public Result<List<ModuleReverseProxyVo>> pageVo(ModuleReverseProxyQueryVo param) {
    ModuleReverseProxyQo qo = param.convert(ModuleReverseProxyQo.class);
    int count = this.count(qo);
    if (0 == count) {
      return Result.successReturn(Collections.<ModuleReverseProxyVo>emptyList()).setCount(count);
    }
    List<ModuleReverseProxyPo> poList = this.list(qo);
    List<ModuleReverseProxyVo> voList = toVoList(poList, qo.getTenantId());

    return Result.successReturn(voList).setCount(count);
  }


}
